package jeopardy.server.game;

import java.io.BufferedReader;
import java.io.File; 
import java.io.FileReader;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import jeopardy.common.protocol.game.play.GameRecordMsg;
import jeopardy.common.protocol.game.play.NewQuestionMsg;
import jeopardy.common.protocol.game.play.QuestionAnswerMsg;
import jeopardy.server.ServerConsts;
import jeopardy.server.db.H2ServerDb;

import org.apache.log4j.Logger;

public class Game implements Runnable{
	
	private static Logger log = Logger.getLogger(Game.class);
	
	private boolean gameStarted = false;
	private BlockingQueue<UserAnswer> answers = new LinkedBlockingQueue<UserAnswer>();
	private List<QuestionAnswerMsg> playedRounds = new LinkedList<QuestionAnswerMsg>();
	private Map<String, Client> activeClients = new HashMap<String, Client>();
	private List<String> initialClients = new LinkedList<String>();
	
	private GameManager manager = null;
	private String gid = null;
	private String creator = null;
	private long startTime;

	private SecureRandom random;
	
	private static ArrayList<QuestionAnswer> questionAnswers = new ArrayList<QuestionAnswer>();
	static{
		try{
			File f = new File("resources/questions.txt");
			BufferedReader br = new BufferedReader(new FileReader(f));
			
			String line = br.readLine();
			while(line != null){
				String q = line.split("-")[1].trim();
				String a = line.split("-")[0].trim();
				questionAnswers.add(new QuestionAnswer(q, a));
				line = br.readLine();
			}
			br.close();
		}catch(Exception e){
			log.info("error reading questions");
			log.debug("error reading questions", e);
		}
	}
	
	public Game(String gid, GameManager gm, H2ServerDb db, String creator){
		this.gid = gid;
		this.manager = gm;
		this.creator = creator;
		this.startTime = System.currentTimeMillis();
		this.random = new SecureRandom();
	}
	
	public boolean joinGame(Client c){
		synchronized(activeClients){
			if(!gameStarted && !activeClients.containsKey(c.getUserName())){
				activeClients.put(c.getUserName(), c);
				initialClients.add(c.getUserName());
				c.joinGame(this);
				return true;
			}else if(initialClients.contains(c.getUserName()) && !activeClients.containsKey(c.getUserName())){
				activeClients.put(c.getUserName(), c);
				c.joinGame(this);
				return true;
			}else{
				return false;
			}
		}
	}
	
	public void leaveGame(Client c){
		synchronized (activeClients) {
			activeClients.remove(c.getUserName());
		}
	}
	
	public String getGid(){
		return gid;
	}
	
	public void submitAnswer(UserAnswer ua){
		answers.offer(ua);
	}
	
	@Override
	public void run() {
		try{
			initUsers();
			gameStarted = true;
			notifyStart();
			
			Thread.sleep(ServerConsts.GAME_START_WAIT_MILLI);	
		
			if(!waitUsers()){
				notifyFinish();
			}else{
				int roundsPlayed = 0;
				while(roundsPlayed < ServerConsts.ROUNDS_PER_GAME && waitUsers()){
					playRound();
					roundsPlayed++;
				}
				
			}
			
		}catch(Exception e){
			log.info("Error playing a game " + gid);
			log.debug("Error playing a game" + gid, e);
		}finally{
			notifyFinish();
		}
	}
	
	private void playRound(){	
		try{
			List<String> eligibleUsers = new LinkedList<String>();
			synchronized(activeClients){
				Iterator<Client> clients = activeClients.values().iterator();
				while(clients.hasNext()){
					eligibleUsers.add(clients.next().getUserName());
				}
			}
		
			int i = random.nextInt(questionAnswers.size());
			
			QuestionAnswer theqa = questionAnswers.get(i);
			NewQuestionMsg q = new NewQuestionMsg(
					theqa.getquestion(), theqa.getAnswer(), ServerConsts.ROUND_TIME, 
					eligibleUsers.toArray(new String[0]), ServerConsts.POINTS_PER_ROUND);
	
			synchronized(activeClients){
				Iterator<Client> clients = activeClients.values().iterator();
				while(clients.hasNext()){
					Client c = clients.next();
					c.tellQuestion(q);
				}
			}
		
			boolean answered = false;
			
			long playStart = System.currentTimeMillis();
			long playUntil = playStart + q.getTimeToPlayMillis();
			
			String winner = null;
			long timeTook = 0L;
			
			while(!answered && System.currentTimeMillis() < playUntil && !eligibleUsers.isEmpty()){
				UserAnswer ua = answers.poll(10, TimeUnit.MILLISECONDS);
				if(ua == null || !theqa.getquestion().equalsIgnoreCase(ua.getquestion())){
					continue;
				}
				timeTook = System.currentTimeMillis() - playStart;
				
				if(eligibleUsers.contains(ua.getUser())){
					eligibleUsers.remove(ua.getUser());
					if(checkAnswer(theqa.getAnswer(), ua.getAnswer())){
						answered = true;
						winner = ua.getUser();
					}
					synchronized(activeClients){
						Iterator<Client> clients = activeClients.values().iterator();
						while(clients.hasNext()){
							clients.next().tellQuestionStatus(ua.getAnswer(), ua.getUser(), timeTook, eligibleUsers.toArray(new String[0]));
						}
					}
				}
			}
		
			QuestionAnswerMsg qa = new QuestionAnswerMsg(q.getQuestion(), q.getAnswer(), winner, q.getPoints(), timeTook);
			synchronized(activeClients){
				Iterator<Client> clients = activeClients.values().iterator();
				while(clients.hasNext()){
					clients.next().tellQuestionFinished(qa);
				}
			}
			answers.clear();
			playedRounds.add(qa);
		}catch(Exception e){
			log.info("Error playing a round: " + gid);
			log.debug("Error playing a round: " + gid, e);
		}
	}
	
	private void notifyFinish(){
		String winner = "";
		int pointsWon = 0;
		Map<String, Integer> scores = new HashMap<String, Integer>();
		
		for(QuestionAnswerMsg qa : playedRounds){
			String w = qa.getWinner();
			int p = qa.getPoints();
			
			if(w != null){
				if(scores.containsKey(w)){
					scores.put(w, scores.get(w) + p);
				}else{
					scores.put(w,  p);
				}
			}
		}
		
		Iterator<String> winners = scores.keySet().iterator();
		while(winners.hasNext()){
			String w = winners.next();
			if(scores.get(w) > pointsWon){
				pointsWon = scores.get(w);
				winner = w;
			}
		}
		
		GameRecordMsg finalRecord = new GameRecordMsg(
				gid, initialClients.toArray(new String[0]), playedRounds.toArray(new QuestionAnswerMsg[0]), 
				creator, winner, pointsWon, startTime );
		
		synchronized(activeClients){
			Iterator<Client> clients = activeClients.values().iterator();
			
			while(clients.hasNext()){
				clients.next().tellFinished(finalRecord);
			}
		}
		manager.reportFinished(finalRecord);	
	}
	
	private void notifyStart(){
		synchronized(activeClients){
			Iterator<Client> clients = activeClients.values().iterator();
			while(clients.hasNext()){
				clients.next().tellGetReady(ServerConsts.GAME_START_WAIT_MILLI, initialClients);
			}
		}
		manager.reportStart(this.gid, this.initialClients);
	}
	
	/**
	 * Wait for at least 2 users, and start the timer
	 * @return
	 */
	private void initUsers(){
		while(activeClients.size() < 2 && activeClients.size() != 0){
			try{
				Thread.sleep(1000);
			}catch(Exception e){
				
			}
		}
		gameStarted = true;
	}
	
	/**
	 * Wait until all the users are here to play.
	 * @return true/false indicating whether all the users are here
	 */
	private boolean waitUsers(){
		long waited = 0;
		long waitStep = 1000;
		while(activeClients.size() != initialClients.size() && waited < ServerConsts.MAX_USER_WAIT_TIME_MILLIS){
			try{
				Thread.sleep(waitStep);
				waited = waited + waitStep;
			}catch(Exception e){
				log.info("error waiting for users");
				log.debug("error waiting for users", e);
			}
		}
		return activeClients.size() > 1;
	}
	
	private boolean checkAnswer(String answer, String userAnswer){
		try{
			answer = answer.toLowerCase();
			userAnswer = userAnswer.toLowerCase();
			
			String qwA = answer.split(" ")[0];
			String qwUa = userAnswer.split(" ")[0];
			
			String restA = answer.substring(answer.indexOf(' '), answer.length());
			String restUa = userAnswer.substring(userAnswer.indexOf(' '), userAnswer.length());
			
			restA = restA.replaceAll(" the ", " ");		
			restA = restA.replaceAll(" is ", " ");
			restA = restA.replaceAll(" are ", " ");
			restA = restA.replaceAll(" a ", " ");
			restA = restA.replaceAll(" an ", " ");

			restUa = restUa.replaceAll(" the ", " ");		
			restUa = restUa.replaceAll(" is ", " ");
			restUa = restUa.replaceAll(" are ", " ");
			restUa = restUa.replaceAll(" a ", " ");
			restUa = restUa.replaceAll(" an ", " ");

			boolean qw = qwA.equalsIgnoreCase(qwUa);
			boolean rest = restA.equalsIgnoreCase(restUa);
			
			return qw && rest;
		}catch(Exception e){
			return false;
		}
	}
}